Skip to content

Add support for composite PKs#24

Merged
renatomassaro merged 6 commits intomainfrom
add-support-for-composite-PKs
Apr 27, 2025
Merged

Add support for composite PKs#24
renatomassaro merged 6 commits intomainfrom
add-support-for-composite-PKs

Conversation

@renatomassaro
Copy link
Copy Markdown
Owner

@renatomassaro renatomassaro commented Apr 27, 2025

Now "templated" (adhoc) queries (and DB.reload/1) support composite PKs.

Summary by CodeRabbit

  • New Features

    • Added support for schemas with composite primary keys, enabling operations on tables like order_items with multiple primary key columns.
    • Introduced a new schema and migration for OrderItems, including relevant fields and composite primary keys.
  • Bug Fixes

    • Improved error handling for schemas without primary keys, ensuring clear runtime errors.
  • Tests

    • Expanded test coverage for composite primary key scenarios and error handling.
    • Added tests to ensure correct SQL generation and query behavior for various schema configurations.
  • Chores

    • Updated test configurations and migration versions to reflect new schema additions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 27, 2025

Walkthrough

This set of changes enhances the database abstraction layer to fully support schemas with composite or custom primary keys. The codebase replaces hardcoded assumptions of a single :id primary key with dynamic retrieval of primary keys from schema metadata. Functions that generate SQL queries, such as fetch, insert, update, and delete, now construct WHERE clauses and bindings based on the actual primary keys of each schema. New error handling is introduced for schemas without primary keys. Test coverage is expanded to include scenarios with composite keys and schemas lacking primary keys. A new schema and migration for a composite key table are added.

Changes

File(s) Change Summary
lib/feeb/db.ex Modified reload/1 to use dynamic primary key retrieval (__primary_keys__/0) instead of hardcoded :id. Raises error if no primary keys exist.
lib/feeb/db/query.ex Refactored query generation to use schema's primary keys; added error handling for missing primary keys; updated function signatures and added helper functions for dynamic SQL generation. Deprecated compile_adhoc_query/2.
lib/feeb/db/schema.ex Added defaulting of @primary_keys to [:id] if not set; exposed __primary_keys__/0; added get_model_from_query_id/1 to retrieve models by query ID.
lib/feeb/db/repo.ex Removed private get_model_from_query_id/1; replaced its usage with Schema.get_model_from_query_id/1.
lib/feeb/db/boot.ex Removed a comment about generating Schema.List from a JSON file; no functional changes.
priv/test/feebdb_schemas.json Added "Elixir.Sample.OrderItems" schema to the test schemas list.
priv/test/migrations/test/250426163848_order_items.sql Added new migration for order_items table with composite primary key (order_id, product_id).
test/db/boot_test.exs Updated expected latest migration version to match the new migration.
test/db/db_test.exs Added tests for OrderItems schema (composite primary keys); added tests for error handling with schemas lacking primary keys.
test/db/query_test.exs Made tests synchronous; added setup to clear query caches; added tests for templated query ID generation with composite and missing primary keys; added log capture for recompilation warnings.
test/support/db/schemas/order_items.ex Added new Sample.OrderItems schema module with composite primary keys and supporting functions.
test/support/db/schemas/all_types.ex Set @primary_keys to nil for Sample.AllTypes schema to test error handling for missing primary keys.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Repo
    participant Schema
    participant Query

    Client->>Repo: reload(schema_instance)
    Repo->>Schema: schema.__primary_keys__()
    alt No primary keys
        Repo->>Client: raise error "No primary keys defined"
    else Primary keys exist
        Repo->>Query: Generate SQL using primary keys
        Query->>Repo: Return result
        Repo->>Client: Return reloaded instance
    end
Loading
sequenceDiagram
    participant Client
    participant Query
    participant Schema

    Client->>Query: get_templated_query_id(query_type, table, meta)
    Query->>Schema: get_model_from_query_id(query_id)
    alt No primary keys
        Query->>Client: raise error "Cannot generate query without primary keys"
    else Primary keys exist
        Query->>Query: generate SQL with dynamic WHERE clause
        Query->>Client: Return query ID and metadata
    end
Loading

Possibly related PRs

  • renatomassaro/FeebDB#23: Introduced the initial reload/1 and reload!/1 functions with hardcoded single primary key :id. The current PR directly builds upon and refines this functionality to support composite and custom primary keys.

Poem

In the warren where queries hop and play,
Now composite keys lead the way!
No more just :id—we check every field,
So your schemas and tables are flexibly healed.
With tests that ensure no bugs sneak by,
This bunny’s proud—give it a try!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3aa895 and 8692009.

📒 Files selected for processing (6)
  • lib/feeb/db.ex (1 hunks)
  • lib/feeb/db/query.ex (7 hunks)
  • lib/feeb/db/repo.ex (1 hunks)
  • lib/feeb/db/schema.ex (3 hunks)
  • test/db/db_test.exs (6 hunks)
  • test/db/query_test.exs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/feeb/db/repo.ex
  • lib/feeb/db.ex
  • test/db/db_test.exs
  • lib/feeb/db/schema.ex
  • lib/feeb/db/query.ex
🔇 Additional comments (18)
test/db/query_test.exs (18)

2-5: Good use of module documentation.

The comment clearly explains why async: false is necessary for this test suite, which is important context for other developers. This helps maintainability by preventing future developers from changing it to async without understanding the implications.


6-6: LGTM - Appropriate use of CaptureLog module.

The import of ExUnit.CaptureLog is correctly added to support testing warning logs in the new test at lines 39-48.


12-14: LGTM - Well-defined paths for test resources.

The module attributes clearly define paths to the SQL files needed for testing the different schema scenarios (standard PK, composite PK, and no PK).


16-20: Good test isolation implementation.

The setup callback ensures each test starts with a clean state by clearing any cached queries. This is especially important for tests involving compiled queries that are stored in a shared cache.


39-48: Well-implemented test for recompilation warning.

This test properly verifies that a warning is logged when the same SQL file is compiled multiple times, providing coverage for the new warning logic in the Query module.


51-64: LGTM - Test for :__all query type.

The test correctly verifies that an :__all query generates SQL to select all records from a table without any WHERE clause.


66-77: LGTM - Test for :__fetch query with standard primary key.

The test verifies that a :__fetch query for a table with a standard primary key generates the expected SQL with a WHERE id = ? clause.


92-105: LGTM - Good error testing for schemas without primary keys.

This test correctly verifies that attempting to use :__fetch on a schema without primary keys raises an appropriate runtime error with a clear message.


107-118: LGTM - Test for :__insert query with all fields.

The test properly verifies that an :__insert query with :all fields generates SQL to insert values for all fields in the table.


120-130: LGTM - Test for :__update query with standard primary key.

The test verifies that an :__update query for a table with a standard primary key generates the expected SQL to update a single field with a WHERE id = ? clause.


132-147: LGTM - Comprehensive test for updating multiple fields.

This test properly verifies:

  1. An :__update query can update multiple fields at once
  2. The generated SQL has the correct SET clauses
  3. The bindings are in the correct order (field values first, then PK values)
  4. The query ID is consistent regardless of field order

This is a good test that covers an important aspect of the update functionality.


149-159: LGTM - Test for :__update with composite primary key.

This test correctly verifies that updating a record in a table with a composite primary key generates the expected SQL with a WHERE order_id = ? AND product_id = ? clause.


161-174: LGTM - Good error testing for update on schemas without primary keys.

This test correctly verifies that attempting to use :__update on a schema without primary keys raises an appropriate runtime error.


176-187: LGTM - Test for :__delete with standard primary key.

The test verifies that a :__delete query for a table with a standard primary key generates the expected SQL with a WHERE id = ? clause.


189-200: LGTM - Test for :__delete with composite primary key.

The test correctly verifies that deleting a record from a table with a composite primary key generates SQL with a WHERE order_id = ? AND product_id = ? clause.


202-215: LGTM - Good error testing for delete on schemas without primary keys.

This test verifies that attempting to use :__delete on a schema without primary keys raises an appropriate runtime error with a clear message.


267-272: LGTM - Comprehensive cache clearing implementation.

The erase_all_query_caches function ensures all relevant query caches are cleared, which is essential for test isolation.


274-275: LGTM - Clean implementation of cache clearing helper.

The erase_query_cache function efficiently uses the :persistent_term module to clear specific cache entries.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
lib/feeb/db/query.ex (1)

84-107: 🛠️ Refactor suggestion

Guard against updates that accidentally modify primary-key columns

If a caller passes a PK in target_fields, the generated SQL will set and filter on the same column, duplicating bind parameters and (worse) allowing PK mutation. Reject such input early:

-    target_fields = Enum.sort(target_fields)
+    primary_keys = model.__primary_keys__()
+
+    # Prevent accidental modifications of PKs
+    if Enum.any?(target_fields, &(&1 in primary_keys)) do
+      raise ArgumentError,
+            "UPDATE cannot target primary keys (got: #{inspect(target_fields)}, " <>
+              "primary keys: #{inspect(primary_keys)})"
+    end
+
+    target_fields = Enum.sort(target_fields)
🧹 Nitpick comments (3)
lib/feeb/db/query.ex (2)

256-266: Nit – duplicated leading space when WHERE is appended

All call-sites already prepend a space (" … #{generate_where_clause()}").
generate_where_clause/1 could omit the initial space to avoid the double-space ("SET … WHERE"). Optional, purely cosmetic.

-    "WHERE #{where_conditions}"
+    "WHERE #{where_conditions}"

268-273: Extend PK presence check to handle [] as “no PKs”

Schemas can explicitly set @primary_keys [], which currently slips through and produces invalid WHERE clauses. Re-use the same error path used for nil.

-defp assert_adhoc_query!(nil, query_id, model) do
+defp assert_adhoc_query!(pk, query_id, model) when pk in [nil, []] do
test/db/query_test.exs (1)

267-276: Consider parametrising cache keys instead of manual erasure

Hard-coding every domain risks future tests forgetting to purge new entries.
A helper that enumerates :persistent_term.get/0 |> :persistent_term.list/0 and deletes keys under {:db_sql_queries, _} would scale better.

Optional improvement; no immediate action required.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aadbd8c and d3aa895.

📒 Files selected for processing (12)
  • lib/feeb/db.ex (1 hunks)
  • lib/feeb/db/boot.ex (0 hunks)
  • lib/feeb/db/query.ex (7 hunks)
  • lib/feeb/db/repo.ex (1 hunks)
  • lib/feeb/db/schema.ex (3 hunks)
  • priv/test/feebdb_schemas.json (1 hunks)
  • priv/test/migrations/test/250426163848_order_items.sql (1 hunks)
  • test/db/boot_test.exs (1 hunks)
  • test/db/db_test.exs (6 hunks)
  • test/db/query_test.exs (3 hunks)
  • test/support/db/schemas/all_types.ex (1 hunks)
  • test/support/db/schemas/order_items.ex (1 hunks)
💤 Files with no reviewable changes (1)
  • lib/feeb/db/boot.ex
🧰 Additional context used
🧬 Code Graph Analysis (2)
lib/feeb/db/repo.ex (1)
lib/feeb/db/schema.ex (1)
  • from_row (272-318)
test/db/db_test.exs (3)
lib/feeb/db.ex (7)
  • begin (24-32)
  • insert! (154-157)
  • update (171-175)
  • delete (205-209)
  • all (135-137)
  • all (139-146)
  • reload (239-251)
lib/feeb/db/sqlite.ex (2)
  • one (59-66)
  • all (55-57)
test/support/db/schemas/friend.ex (2)
  • new (17-21)
  • update (23-26)
🔇 Additional comments (20)
priv/test/feebdb_schemas.json (1)

1-1: Addition of OrderItems schema looks good.

The schema list now includes the new Elixir.Sample.OrderItems schema which has been created to support composite primary keys testing.

test/support/db/schemas/all_types.ex (1)

8-8: Good addition of explicit nil primary key.

Setting @primary_keys nil explicitly marks this schema as having no primary keys, which is a good test case for the new architecture supporting both composite PKs and schemas without PKs.

priv/test/migrations/test/250426163848_order_items.sql (1)

1-9: Well-structured composite PK table definition.

This SQL migration creates a table with a composite primary key (order_id, product_id) which is perfect for testing the new composite primary key support. The STRICT enforcement is also a good practice for SQLite tables.

lib/feeb/db/repo.ex (4)

348-348: Good refactoring to centralize model retrieval.

Replaced the private function with Schema.get_model_from_query_id/1, centralizing the model retrieval logic in the Schema module as part of supporting composite primary keys.


353-353: Consistent refactoring pattern applied.

Model retrieval logic consistently refactored to use the Schema module's implementation for insert operations.


358-358: Consistent refactoring pattern maintained.

Update operations now also use the centralized model retrieval, maintaining consistent architecture throughout the codebase.


363-363: Refactoring completed for all query types.

Delete operations also use the centralized model retrieval, completing the refactoring across all database operation types.

test/db/boot_test.exs (1)

27-27: Version update aligns with new migration for composite primary keys.

The version change corresponds to the new migration that adds the order_items table with composite primary keys.

lib/feeb/db.ex (2)

240-244: Good enhancement to support schemas with composite or no primary keys.

The code now dynamically retrieves primary keys and adds proper error handling when none exist, which is essential for the composite PK support.


246-248: Properly maps multiple primary key values to bindings.

This change correctly maps over all primary keys to fetch their corresponding values from the struct, supporting the reload of entities with composite keys.

test/support/db/schemas/order_items.ex (3)

1-7: Well-structured schema with explicit composite primary keys.

The schema correctly defines the context, table name, and explicitly sets composite primary keys.


9-16: Schema fields are well defined with appropriate types.

The schema includes all necessary fields with proper types, including the composite primary keys and timestamp fields with UTC datetime types.


18-27: Standard schema operations implemented properly.

The new/1 and update/2 functions follow the established pattern in the codebase for creating and updating schema instances.

test/db/db_test.exs (5)

5-5: Added OrderItems to test aliases.

Appropriate inclusion of the new OrderItems schema in the test module's aliases.


409-430: Good test coverage for fetch queries with different PK types.

These tests verify that the :fetch templated query works correctly with both simple schemas (single PK) and schemas with composite primary keys. The test cases cover successful retrieval and non-existent records.


586-708: Comprehensive tests for CRUD operations with composite PKs.

These tests thoroughly verify that insert, update, and delete operations work correctly with the OrderItems schema that has composite primary keys. All important scenarios are covered including:

  1. Creation of new records
  2. Updating existing records
  3. Deletion of records
  4. Verifying the operations succeed through subsequent queries

793-809: Good test for reloading schemas with composite PKs.

This test verifies that the reload function correctly handles schemas with composite primary keys, ensuring that updates are properly reflected after reloading.


879-893: Proper error handling for schemas without PKs.

This test correctly verifies that attempting to reload a schema without primary keys raises an appropriate runtime error with a clear message.

lib/feeb/db/schema.ex (1)

114-115: Nice touch – public accessor for PKs

Exposing __primary_keys__/0 will considerably simplify PK-introspection across the codebase.
👍 Nothing to change here.

test/db/query_test.exs (1)

16-20: 👍 Good call clearing persistent-term caches in setup/0

Ensures deterministic tests despite global storage. No action needed.

@renatomassaro renatomassaro force-pushed the add-support-for-composite-PKs branch from d3aa895 to 8692009 Compare April 27, 2025 12:05
@renatomassaro renatomassaro merged commit 55926a0 into main Apr 27, 2025
5 checks passed
@renatomassaro renatomassaro deleted the add-support-for-composite-PKs branch April 27, 2025 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant